/*
 * Copyright (c) 2012 Kent Ryhorchuk
 *
 * Use of this source code is governed by a MIT-style
 * license that can be found in the LICENSE file or at
 * https://opensource.org/licenses/MIT
 */
#include <stdarg.h>
#include <lk/reg.h>
#include <lk/debug.h>
#include <stdio.h>
#include <assert.h>
#include <lib/cbuf.h>
#include <kernel/thread.h>
#include <platform/debug.h>
#include <arch/ops.h>
#include <dev/uart.h>
#include <target/debugconfig.h>
#include <fsl_common.h>
#include <arch/arm/cm.h>
#include "fsl_lpuart.h"

#ifdef ENABLE_UART1
cbuf_t uart1_rx_buf;
#ifndef UART1_FLOWCONTROL
#define UART1_FLOWCONTROL 0
#endif
#ifndef UART1_BAUDRATE
#define UART1_BAUDRATE 115200
#endif
#ifndef UART1_RXBUF_SIZE
#define UART1_RXBUF_SIZE 64
#endif
#endif

#ifdef ENABLE_UART2
cbuf_t uart2_rx_buf;
#ifndef UART2_FLOWCONTROL
#define UART2_FLOWCONTROL 0
#endif
#ifndef UART2_BAUDRATE
#define UART2_BAUDRATE 115200
#endif
#ifndef UART2_RXBUF_SIZE
#define UART2_RXBUF_SIZE 64
#endif
#endif

#ifdef ENABLE_UART3
cbuf_t uart3_rx_buf;
#ifndef UART3_FLOWCONTROL
#define UART3_FLOWCONTROL 0
#endif
#ifndef UART3_BAUDRATE
#define UART3_BAUDRATE 115200
#endif
#ifndef UART3_RXBUF_SIZE
#define UART3_RXBUF_SIZE 64
#endif
#endif

#ifdef ENABLE_UART4
cbuf_t uart4_rx_buf;
#ifndef UART4_FLOWCONTROL
#define UART4_FLOWCONTROL 0
#endif
#ifndef UART4_BAUDRATE
#define UART4_BAUDRATE 115200
#endif
#ifndef UART4_RXBUF_SIZE
#define UART4_RXBUF_SIZE 64
#endif
#endif


#ifdef ENABLE_UART5
cbuf_t uart5_rx_buf;
#ifndef UART5_FLOWCONTROL
#define UART5_FLOWCONTROL 0
#endif
#ifndef UART5_BAUDRATE
#define UART5_BAUDRATE 115200
#endif
#ifndef UART5_RXBUF_SIZE
#define UART5_RXBUF_SIZE 64
#endif
#endif


#ifdef ENABLE_UART1
static void Usart1RxIrqCallback(void);
static void Usart1ErrIrqCallback(void);
#endif

#ifdef ENABLE_UART2
static void Usart2RxIrqCallback(void);
static void Usart2ErrIrqCallback(void);
#endif

#ifdef ENABLE_UART3
static void Usart3RxIrqCallback(void);
static void Usart3ErrIrqCallback(void);
#endif

#ifdef ENABLE_UART4
static void Usart4RxIrqCallback(void);
static void Usart4ErrIrqCallback(void);
#endif

#ifdef ENABLE_UART5
static void Usart5RxIrqCallback(void);
static void Usart5ErrIrqCallback(void);
#endif


static uint32_t GetUartSrcFreq(void)
{
    uint32_t freq;

    /* To make it simple, we assume default PLL and divider settings, and the only variable
       from application is use PLL3 source or OSC source */
    if (CLOCK_GetMux(kCLOCK_UartMux) == 0) /* PLL3 div6 80M */
    {
        freq = (CLOCK_GetPllFreq(kCLOCK_PllUsb1) / 6U) / (CLOCK_GetDiv(kCLOCK_UartDiv) + 1U);
    }
    else
    {
        freq = CLOCK_GetOscFreq() / (CLOCK_GetDiv(kCLOCK_UartDiv) + 1U);
    }

    return freq;
}

static void usart_init1_early(LPUART_Type *usart, uint32_t baud, uint16_t flowcontrol) {
    lpuart_config_t config;

    LPUART_GetDefaultConfig(&config);

    config.enableRx = true;
    config.enableTx = true;
    config.dataBitsCount = kLPUART_EightDataBits;
    config.stopBitCount = kLPUART_OneStopBit;
    config.parityMode = kLPUART_ParityDisabled;

    LPUART_Init(usart, &config, GetUartSrcFreq());
}


void uart_init_early(void) {
#ifdef ENABLE_UART1
    usart_init1_early(LPUART1, UART1_BAUDRATE, UART1_FLOWCONTROL);
#endif

#ifdef ENABLE_UART2
    usart_init1_early(LPUART2, UART2_BAUDRATE, UART2_FLOWCONTROL);
#endif

#ifdef ENABLE_UART3
    usart_init1_early(LPUART3, UART3_BAUDRATE, UART3_FLOWCONTROL);
#endif

#ifdef ENABLE_UART4
    usart_init1_early(LPUART4, UART4_BAUDRATE, UART4_FLOWCONTROL);
#endif

#ifdef ENABLE_UART5
    usart_init1_early(LPUART5, UART5_BAUDRATE, UART5_FLOWCONTROL);
#endif
}

void uart_init(void) {

#ifdef ENABLE_UART1
	cbuf_initialize(&uart1_rx_buf, UART1_RXBUF_SIZE);
    LPUART_EnableInterrupts(LPUART1, kLPUART_RxDataRegFullInterruptEnable);
    EnableIRQ(LPUART1_IRQn);       
#endif
#ifdef ENABLE_UART2
    cbuf_initialize(&uart2_rx_buf, UART2_RXBUF_SIZE);
    LPUART_EnableInterrupts(LPUART2, kLPUART_RxDataRegFullInterruptEnable);
    EnableIRQ(LPUART2_IRQn);    
#endif
#ifdef ENABLE_UART3
    cbuf_initialize(&uart3_rx_buf, UART3_RXBUF_SIZE);
    LPUART_EnableInterrupts(LPUART3, kLPUART_RxDataRegFullInterruptEnable);
    EnableIRQ(LPUART3_IRQn);    
#endif
#ifdef ENABLE_UART4
    cbuf_initialize(&uart4_rx_buf, UART4_RXBUF_SIZE);
    LPUART_EnableInterrupts(LPUART4, kLPUART_RxDataRegFullInterruptEnable);
    EnableIRQ(LPUART4_IRQn);
#endif
#ifdef ENABLE_UART5
    cbuf_initialize(&uart5_rx_buf, UART5_RXBUF_SIZE);
    LPUART_EnableInterrupts(LPUART5, kLPUART_RxDataRegFullInterruptEnable);
    EnableIRQ(LPUART5_IRQn);
#endif

}

void uart_rx_irq(LPUART_Type *usart, cbuf_t *rxbuf) {
    arm_cm_irq_entry();

    bool resched = false;
    while ((kLPUART_RxDataRegFullFlag)&LPUART_GetStatusFlags(usart)) {
        if (!cbuf_space_avail(rxbuf)) {
            // Overflow - let flow control do its thing by not
            // reading the from the FIFO.
          LPUART_DisableInterrupts(usart, kLPUART_RxDataRegFullInterruptEnable);
          break;
        }

        char c = LPUART_ReadByte(usart);
        cbuf_write_char(rxbuf, c, false);
        resched = true;
    }

    arm_cm_irq_exit(resched);
}

#ifdef ENABLE_UART1
void rt1020_LPUART1_IRQ(void) {
    uart_rx_irq(LPUART1, &uart1_rx_buf);
}
#endif

#ifdef ENABLE_UART2
void rt1020_LPUART2_IRQ(void) {
    uart_rx_irq(LPUART2, &uart2_rx_buf);
}
#endif

#ifdef ENABLE_UART3
void rt1020_LPUART3_IRQ(void) {
    uart_rx_irq(LPUART3, &uart3_rx_buf);
}
#endif

#ifdef ENABLE_UART4
void rt1020_LPUART4_IRQ(void) {
    uart_rx_irq(LPUART4, &uart4_rx_buf);
}
#endif

#ifdef ENABLE_UART5
void rt1020_LPUART5_IRQ(void) {
    uart_rx_irq(LPUART5, &uart5_rx_buf);
}
#endif

static void usart_putc(LPUART_Type *usart, char c) {
    while(!(LPUART_GetStatusFlags(usart) & kLPUART_TxDataRegEmptyFlag));
    LPUART_WriteByte(usart, c);
}

static int usart_getc(LPUART_Type *usart, cbuf_t *rxbuf, bool wait) {
    unsigned char c;
    if (cbuf_read_char(rxbuf, (char *) &c, wait) == 0)
        return -1;
    if (cbuf_space_avail(rxbuf) > cbuf_size(rxbuf))
		  LPUART_EnableInterrupts(usart, kLPUART_RxDataRegFullInterruptEnable);

    return c;
}

static LPUART_Type *get_usart(int port) {
    switch (port) {
#ifdef ENABLE_UART1
        case 1:
            return LPUART1;
#endif
#ifdef ENABLE_UART2
        case 2:
            return LPUART2;
#endif
#ifdef ENABLE_UART3
        case 3:
            return LPUART3;
#endif
#ifdef ENABLE_UART4
        case 4:
            return LPUART4;
#endif
#ifdef ENABLE_UART5
        case 5:
            return LPUART5;
#endif
        default:
            ASSERT(false);
            return 0;
    }
}

static cbuf_t *get_rxbuf(int port) {
    switch (port) {
#ifdef ENABLE_UART1
        case 1:
            return &uart1_rx_buf;
#endif
#ifdef ENABLE_UART2
        case 2:
            return &uart2_rx_buf;
#endif
#ifdef ENABLE_UART3
        case 3:
            return &uart3_rx_buf;
#endif
#ifdef ENABLE_UART4
        case 4:
            return &uart4_rx_buf;
#endif            
#ifdef ENABLE_UART5
        case 5:
            return &uart5_rx_buf;
#endif
        default:
            ASSERT(false);
            return 0;
    }
}

int uart_putc(int port, char c) {
    LPUART_Type *usart = get_usart(port);
    usart_putc(usart, c);
    return 1;
}

int uart_getc(int port, bool wait) {
    cbuf_t *rxbuf = get_rxbuf(port);
    LPUART_Type *usart = get_usart(port);

    return usart_getc(usart, rxbuf, wait);
}

void uart_flush_tx(int port) {}

void uart_flush_rx(int port) {}

void uart_init_port(int port, uint baud) {
    LPUART_Type *usart = get_usart(port);

    LPUART_SetBaudRate(usart, baud, GetUartSrcFreq());
}

// inject a character into the console uart rx buffer
void __debugger_console_putc(char c) {
    cbuf_t *rxbuf = get_rxbuf(DEBUG_UART);
    if (rxbuf && cbuf_space_avail(rxbuf)) {
        cbuf_write_char(rxbuf, c, false);
    }
}

